﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using JQuery.WebKit.Common.Utilities;
using JQuery.WebKit.WebCommon.Attributes;

namespace JQuery.WebKit.WebCommon
{
    public enum IDType
    {
        Server = 0,
        Client = 1
    }

    public static class HtmlUtil
    {
        private static readonly Dictionary<Type, IList<string>> _cssCache = new Dictionary<Type, IList<string>>();
        private static readonly object _sync = new object();
        private static readonly bool _isMinifierEnabled = true;

        #region public static HtmlTableRow AddRow(HtmlTable table, params string[] texts)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="table"></param>
        /// <param name="texts"></param>
        /// <returns></returns>
        public static HtmlTableRow AddRow(HtmlTable table, params string[] texts)
        {
            if (table == null)
            {
                table = new HtmlTable();
            }
            return InsertRow(table.Rows.Count, table, texts);
        }
        #endregion

        #region public static HtmlTableRow InsertRow(int index, HtmlTable table, params string[] texts)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="table"></param>
        /// <param name="texts"></param>
        /// <returns></returns>
        public static HtmlTableRow InsertRow(int index, HtmlTable table, params string[] texts)
        {
            if (table == null)
            {
                table = new HtmlTable();
            }
            HtmlTableRow row = InsertRow(index, table);
            foreach (string str in texts)
            {
                AddCell(row).InnerHtml = str;
            }
            return row;
        }
        #endregion

        #region public static HtmlTableRow InsertRow(int index, HtmlTable table)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="table"></param>
        /// <returns></returns>
        public static HtmlTableRow InsertRow(int index, HtmlTable table)
        {
            if (table == null)
            {
                table = new HtmlTable();
            }
            HtmlTableRow row = new HtmlTableRow();
            table.Rows.Insert(index, row);
            return row;
        }
        #endregion

        #region public static HtmlTableCell AddCell(HtmlTableRow row)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        public static HtmlTableCell AddCell(HtmlTableRow row)
        {
            HtmlTableCell cell = new HtmlTableCell();
            row.Cells.Add(cell);
            return cell;
        }
        #endregion

        #region public static bool RegisterResource(Type controlType, string path, ResourceType resourceType)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="controlType"></param>
        /// <param name="path"></param>
        /// <param name="resourceType"></param>
        /// <returns></returns>
        public static bool RegisterResource(Type controlType, string path, ResourceType resourceType, bool registerInHeader = true)
        {
            if (HttpContext.Current == null)
            {
                return false;
            }
            Page currentPage = HttpContext.Current.Handler as Page;
            if (currentPage == null)
            {
                return false;
            }
            return RegisterResource(currentPage, controlType, path, resourceType, registerInHeader);
        }
        #endregion

        #region public static bool RegisterResource(Page page, Type controlType, string path, ResourceType resourceType)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="page"></param>
        /// <param name="controlType"></param>
        /// <param name="path"></param>
        /// <param name="resourceType"></param>
        /// <returns></returns>
        public static bool RegisterResource(Page page, Type controlType, string path, ResourceType resourceType, bool registerInHeader = true)
        {
            if (page == null)
            {
                return false;
            }

            ClientScriptManager manager = page.ClientScript;
            ScriptManager ajaxManager = ScriptManager.GetCurrent(page);

            bool useAjax = false;
            if (ajaxManager != null && ajaxManager.IsInAsyncPostBack)
            {
                useAjax = true;
            }
           
            string url = GetWebResourceUrl(page, controlType, path, resourceType);// page.ResolveClientUrl(manager.GetWebResourceUrl(controlType, path));

            if (ajaxManager == null || !useAjax)
            {
                if (resourceType == ResourceType.Javascript && !registerInHeader)
                {
                    if (!manager.IsClientScriptIncludeRegistered(path))
                    {
                        manager.RegisterClientScriptInclude(controlType, path, url);
                        return true;
                    }
                }
                else
                {
                    if (!HeadScriptManager.IsHeaderResourceRegistered(controlType, url))
                    {
                        return HeadScriptManager.RegisterHeaderResource(page, controlType, url, resourceType);
                    }
                }
            }
            else
            {
                if (resourceType == ResourceType.Javascript)
                {
                    if (!manager.IsClientScriptBlockRegistered(path))
                    {
                        ScriptManager.RegisterClientScriptBlock(page, controlType, path, string.Format("<script src='{0}' type='text/javascript'></script>", url), false);
                        return true;
                    }
                }
                else
                {
                    bool isRegistered = HeadScriptManager.IsHeaderResourceRegistered(controlType, url);
                    if (!isRegistered)
                    {
                        isRegistered = HeadScriptManager.RegisterHeaderResource(page, controlType, url, resourceType);
                        if (useAjax && !manager.IsClientScriptBlockRegistered(path))
                        {
                            ScriptManager.RegisterClientScriptBlock(page, controlType, path,
                                "var head = document.getElementsByTagName('HEAD');" +
                                "if (head && head[0]) {" +
                                    "var linkElement = document.createElement('link');" +
                                    "linkElement.type = 'text/css';" +
                                    "linkElement.rel = 'stylesheet';" +
                                    "linkElement.href = '" + url + "';" +
                                    "head[0].appendChild(linkElement);" +
                                "}"
                                , true);
                            isRegistered = true;
                        }
                        return isRegistered;
                    }
                }
            }


            return false;
        }
        #endregion

        internal static string GetWebResourceUrl(Page page, Type controlType, string path, ResourceType resourceType)
        {
            if (resourceType == ResourceType.Javascript && _isMinifierEnabled)
            {
                if (Debugger.IsAttached)
                {
                    if (path.EndsWith(".min.js"))
                    {
                        string localPath = path.Substring(0, path.Length - 7) + ".js";
                        bool exists = EnsureWebResourceValid(localPath, controlType.Assembly, false);
                        if (exists)
                        {
                            path = localPath;
                        }
                    }
                }
                else if (!path.EndsWith(".min.js"))
                {
                    string localPath = path.Substring(0, path.Length - 3) + ".min.js";
                    bool exists = EnsureWebResourceValid(localPath, controlType.Assembly, false);
                    if (exists)
                    {
                        path = localPath;
                    }
                }
            }
            Debug.Assert(EnsureWebResourceValid(path, controlType.Assembly, true));
            return page.ResolveClientUrl(page.ClientScript.GetWebResourceUrl(controlType, path));
        }

        #region public static bool RegisterScript(Page page, Type controlType, string scriptWithoutTags)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="page"></param>
        /// <param name="controlType"></param>
        /// <param name="scriptWithoutTags"></param>
        /// <returns></returns>
        public static bool RegisterScript(Page page, Type controlType, string scriptWithoutTags, bool registerAtTheEnd)
        {
            if (page == null || string.IsNullOrEmpty(scriptWithoutTags))
            {
                return false;
            }

            ScriptManager ajaxManager = ScriptManager.GetCurrent(page);
            string key = scriptWithoutTags.GetHashCode().ToString();
            if (ajaxManager == null)
            {
                ClientScriptManager manager = page.ClientScript;
                if (registerAtTheEnd)
                {
                    if (!manager.IsStartupScriptRegistered(key))
                    {
                        manager.RegisterStartupScript(controlType, key, scriptWithoutTags, true);
                        return true;
                    }
                }
                else if (!manager.IsClientScriptBlockRegistered(key))
                {
                    manager.RegisterClientScriptBlock(controlType, key, scriptWithoutTags, true);
                    return true;
                }
            }
            else //if (!ajaxManager.IsInAsyncPostBack)
            {
                if (registerAtTheEnd)
                {
                    ScriptManager.RegisterStartupScript(page, controlType, key, scriptWithoutTags, true);
                }
                else
                {
                    ScriptManager.RegisterClientScriptBlock(page, controlType, key, scriptWithoutTags, true);
                }
                return true;
            }
            return false;
        }
        #endregion

        public static void RegisterScript(Control control, string scriptWithoutTags)
        {
            string script = scriptWithoutTags;
            if (!script.Trim().EndsWith("</script>"))
            {
                script = string.Format("<script type='text/javascript'>{0}</script>", scriptWithoutTags);
            }
            List<LiteralControl> children = control.GetChildren<LiteralControl>();
            if (children.Count(item => item.Text == script) == 0)
            {
                control.Controls.Add(new LiteralControl(script));
            }
        }

        #region public static bool IsScriptRegistered(Page page, string scriptWithoutTags, bool registeredAtTheEnd)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="page"></param>
        /// <param name="scriptWithoutTags"></param>
        /// <param name="registeredAtTheEnd"></param>
        /// <returns>True if script is registered, otherwise false.</returns>
        public static bool IsScriptRegistered(Page page, string scriptWithoutTags, bool registeredAtTheEnd)
        {
            if (page == null || string.IsNullOrEmpty(scriptWithoutTags))
            {
                return false;
            }

            ScriptManager ajaxManager = ScriptManager.GetCurrent(page);
            string key = scriptWithoutTags.GetHashCode().ToString();
            if (ajaxManager == null)
            {
                ClientScriptManager manager = page.ClientScript;
                if (registeredAtTheEnd)
                {
                    return manager.IsStartupScriptRegistered(key);
                }
                else
                {
                    manager.IsClientScriptBlockRegistered(key);
                }
            }
            else
            {
                ReadOnlyCollection<RegisteredScript> scripts;
                if (registeredAtTheEnd)
                {
                    scripts = ajaxManager.GetRegisteredStartupScripts();
                }
                else
                {
                    scripts = ajaxManager.GetRegisteredClientScriptBlocks();
                }
                foreach (RegisteredScript script in scripts)
                {
                    if (script.Key == key)
                    {
                        return true;
                    }
                }

                return false;
            }
            return false;
        }
        #endregion

        #region public static void RegisterArrayDeclaration(Page page, string arrayName, string arrayValue)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="page"></param>
        /// <param name="arrayName"></param>
        /// <param name="arrayValue"></param>
        public static void RegisterArrayDeclaration(Page page, string arrayName, string arrayValue)
        {
            ScriptManager ajaxManager = ScriptManager.GetCurrent(page);
            if (ajaxManager == null)
            {
                page.ClientScript.RegisterArrayDeclaration(arrayName, arrayValue);
            }
            else
            {
                ScriptManager.RegisterArrayDeclaration(page, arrayName, arrayValue);
            }
        }
        #endregion

        #region private static T FindControl<T>(ControlCollection controlCollection)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="controlCollection"></param>
        /// <returns></returns>
        public static T FindControl<T>(this ControlCollection controlCollection)
            where T : Control
        {
            foreach (Control c in controlCollection)
            {
                if (c.GetType() == typeof(T))
                    return (T)c;
                else
                {
                    T child = FindControl<T>(c.Controls);
                    if (child != null)
                        return child;
                }
            }
            return null;
        }
        #endregion

        public static Control FindControlRecursive(this Control root, string id, IDType idType)
        {
            if (idType == IDType.Client && root.ClientID == id)
            {
                return root;
            }
            if (idType == IDType.Server && root.ID == id)
            {
                return root;
            }

            ControlCollection children = root is IWebControl ? (root as IWebControl).GetChildren() : root.Controls;
            foreach (Control c in children)
            {
                Control t = FindControlRecursive(c, id, idType);
                if (t != null)
                {
                    return t;
                }
            }
            return null;
        }

        public static Control FindControlRecursive(this Control root, Predicate<Control> predicate)
        {
            bool isValid = predicate != null && predicate(root) || predicate == null;
            if (isValid)
            {
                return root;
            }

            ControlCollection children = root is IWebControl ? (root as IWebControl).GetChildren() : root.Controls;
            foreach (Control c in children)
            {
                Control t = c.FindControlRecursive(predicate);
                if (t != null)
                {
                    return t;
                }
            }
            return null;
        }

        #region public static T FindParent<T>(this Control control)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="control"></param>
        /// <returns></returns>
        public static T FindParent<T>(this Control control)
            where T : Control
        {
            return control.FindParent<T>(null);
        }
        #endregion

        public static T FindParent<T>(this Control control, Predicate<T> predicate)
            where T : Control
        {
            return control.FindParent<T>(true, predicate);
        }

        private static T FindParent<T>(this Control control, bool initialControl, Predicate<T> predicate)
           where T : Control
        {
            Control parent = initialControl ? control.Parent : control;
            bool isValid = (predicate != null && parent is T && predicate(parent as T)) || predicate == null;
            if (parent is T && isValid)
            {
                return parent as T;
            }
            else if (parent.Parent != null)
            {
                return FindParent<T>(parent.Parent, false, predicate);
            }
            return null;
        }


        #region public static List<T> GetChildren<T>(this Control control)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="control"></param>
        /// <returns></returns>
        public static List<T> GetChildren<T>(this Control control)
            where T : Control
        {
            return control.GetChildren<T>(true);
        }
        #endregion

        public static List<T> GetChildren<T>(this Control control, bool recursive) where T : Control
        {
            control.GetType().GetMethod("EnsureChildControls", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic).Invoke(control, null);
            List<T> lst = new List<T>();

            ControlCollection controls = control is IWebControl ? (control as IWebControl).GetChildren() : control.Controls;
            foreach (Control c in controls)
            {
                if (c is T)
                {
                    lst.Add((T)c);
                }

                if (recursive)
                {
                    List<T> childLst = GetChildren<T>(c, recursive);
                    foreach (Control child in childLst)
                    {
                        lst.Add((T)child);
                    }
                }
            }
            return lst;
        }

        //public static List<T> GetChildren<T>(this Control control, Predicate<Control> predicate) where T : Control
        //{
        //    control.GetType().GetMethod("EnsureChildControls", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic).Invoke(control, null);
        //    List<T> lst = new List<T>();

        //    ControlCollection controls = control is IWebControl ? (control as IWebControl).GetChildren() : control.Controls;
        //    foreach (Control c in controls)
        //    {
        //        if (predicate(c))
        //        {
        //            if (c is T)
        //                lst.Add((T)c);
        //            else
        //            {
        //                List<T> childLst = GetChildren<T>(c, predicate);
        //                foreach (Control child in childLst)
        //                {
        //                    lst.Add((T)child);
        //                }
        //            }
        //        }
        //    }
        //    return lst;
        //}

        public static List<T> GetChildren<T>(this Control control, Predicate<T> predicate) where T : Control
        {
            control.GetType().GetMethod("EnsureChildControls", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic).Invoke(control, null);
            List<T> lst = new List<T>();

            ControlCollection controls = control is IWebControl ? (control as IWebControl).GetChildren() : control.Controls;
            foreach (Control c in controls)
            {
                if (c is T && predicate(c as T))
                {
                    lst.Add((T)c);
                }
                else
                {
                    List<T> childLst = GetChildren<T>(c, predicate);
                    foreach (Control child in childLst)
                    {
                        lst.Add((T)child);
                    }
                }
            }
            return lst;
        }

        public static List<T> GetContainedChildren<T>(this Control control, Predicate<T> predicate) where T : Control
        {
            return control.GetContainedChildren(control.GetType(), true, predicate);
        }

        private static List<T> GetContainedChildren<T>(this Control control, Type parentType, bool isTopParent, Predicate<T> predicate) where T : Control
        {
            control.GetType().GetMethod("EnsureChildControls", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic).Invoke(control, null);
            List<T> lst = new List<T>();

            ControlCollection controls = control is IWebControl ? (control as IWebControl).GetChildren() : control.Controls;
            foreach (Control c in controls)
            {
                if (c is T && predicate(c as T))
                {
                    lst.Add((T)c);
                }
                else if ((c.GetType() == parentType && !isTopParent) || (c.GetType() != parentType))
                {
                    List<T> childLst = GetContainedChildren<T>(c, parentType, false, predicate);
                    foreach (Control child in childLst)
                    {
                        lst.Add((T)child);
                    }
                }
            }
            return lst;
        }

        //public static List<T> GetChildren<T, ParentT>(this Control control, Predicate<ParentT> predicate) where T : Control
        //{
        //    control.GetType().GetMethod("EnsureChildControls", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic).Invoke(control, null);
        //    List<T> lst = new List<T>();

        //    ControlCollection controls = control is IWebControl ? (control as IWebControl).GetChildren() : control.Controls;
        //    foreach (Control c in controls)
        //    {
        //        if (c is T)
        //        {
        //            lst.Add((T)c);
        //        }
        //        else
        //        {
        //            List<T> childLst = GetChildren<T, ParentT>(c, predicate);
        //            foreach (Control child in childLst)
        //            {
        //                lst.Add((T)child);
        //            }
        //        }
        //    }
        //    return lst;
        //}

        #region public static bool EnsureWebResourceValid(string resName, Assembly asm, bool throwException)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="resName"></param>
        /// <param name="asm"></param>
        /// <param name="throwException"></param>
        /// <returns></returns>
        public static bool EnsureWebResourceValid(string resName, Assembly asm, bool throwException)
        {
            if (resName.StartsWith("/") | resName.StartsWith("http://"))
            {
                //is an url
                return true;
            }
            bool bRet = EnsureManifestResourceExist(resName, asm, throwException);

            if (bRet == true)
            { //find the attribute
                bRet = false;
                // Iterate through the attributes for the assembly.
                foreach (Attribute attr in Attribute.GetCustomAttributes(asm))
                {
                    //Check for WebResource attributes.
                    if (attr.GetType() == typeof(WebResourceAttribute))
                    {
                        WebResourceAttribute wra = (WebResourceAttribute)attr;
                        //Debug.WriteLine("Resource in the assembly: " + wra.WebResource.ToString() +
                        //  " with ContentType = " + wra.ContentType.ToString() +
                        //  " and PerformsSubstitution = " + wra.PerformSubstitution.ToString());
                        if (wra.WebResource == resName)
                        {
                            bRet = true;
                            break;
                        }
                    }
                }//foreach
            } //ManifestResourceExist
            if (bRet == false)
            {
                string sMsg = "Embedded resource " + resName + " in assembly " + asm.FullName + " doesn't have WebResourceAttribute ";
                if (throwException == true)
                {
                    throw new ApplicationException(sMsg);
                }
                else
                {
                    //Debug.Assert(false, sMsg);
                }
            }
            return bRet;
        }
        #endregion

        #region public static bool EnsureManifestResourceExist(string resName, Assembly asm, bool throwException)
        /// <summary>
        /// if ThrowException=true, then Throw exception if resource not found
        /// </summary>
        /// <param name="resName"></param>
        /// <param name="asm"></param>
        /// <param name="throwException"></param>
        /// <returns></returns>
        public static bool EnsureManifestResourceExist(string resName, Assembly asm, bool throwException)
        {   //NOTE: If resource is located in subfolder of C# project, path of subfolder should be specified (it is included as part of namespace)
            //' Resources are named using a fully qualified name ((including namespace).
            bool bRet = true;
            ManifestResourceInfo info = asm.GetManifestResourceInfo(resName);
            if (info == null)
            {
                bRet = false;
                string sMsg = "Couldn't find embedded resource " + resName + " in assembly " + asm.FullName;
                if (throwException)
                {
                    throw new ApplicationException(sMsg);
                }
                else
                {
                    //Debug.Assert(false, sMsg);
                }
            }

            return bRet;
        }
        #endregion

        #region public static string JSName(this PropertyInfo propertyInfo)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static string JSName(this PropertyInfo propertyInfo)
        {
            if (propertyInfo == null)
            {
                return string.Empty;
            }
            System.ComponentModel.AttributeCollection attributes =
                    TypeDescriptor.GetProperties(propertyInfo.DeclaringType)[propertyInfo.Name].Attributes;

            JavaScriptAttribute jsAttribute =
               (JavaScriptAttribute)attributes[typeof(JavaScriptAttribute)];

            if (jsAttribute != null)
            {
                return jsAttribute.Name;
            }
            return string.Empty;
        }
        #endregion

        #region public static string AddCssClass(string cssClass, string newClassName)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cssClass"></param>
        /// <param name="newClassName"></param>
        /// <returns></returns>
        public static string AddCssClass(this string cssClass, string newClassName, bool addFirst)
        {
            if (string.IsNullOrEmpty(newClassName))
            {
                return cssClass;
            }
            string[] cPrevNames = new string[0];
            if (!string.IsNullOrEmpty(cssClass))
            {
                cPrevNames = cssClass.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            }
            string[] cNewNames = newClassName.Split(new char[] { ' ', '.' }, StringSplitOptions.RemoveEmptyEntries);
            string ret = string.Empty;
            ret = string.Join(" ", cPrevNames);
            List<string> lst = new List<string>(cPrevNames);

            if (addFirst)
            {
                ret = string.Format("{0} {1}", string.Join(" ", cNewNames.Where(item => !lst.Contains(item))), ret);
            }
            else
            {
                ret = string.Format("{0} {1}", ret, string.Join(" ", cNewNames.Where(item => !lst.Contains(item))));
            }

            return ret.Trim();
        }
        #endregion

        #region public static string RemoveCssClass(string cssClass, string classNameToRemove)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cssClass"></param>
        /// <param name="classNameToRemove"></param>
        /// <returns></returns>
        public static string RemoveCssClass(this string cssClass, string classPatternNameToRemove)
        {
            string[] cPrevNames = cssClass.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            string[] cRemovableNames = classPatternNameToRemove.Split(new char[] { ' ', '.' }, StringSplitOptions.RemoveEmptyEntries);
            string ret = string.Empty;

            List<string> lst = new List<string>(cRemovableNames);
            foreach (string str in cPrevNames)
            {
                Wildcard wildcard = new Wildcard(classPatternNameToRemove);

                if (lst.Count(item => wildcard.IsMatch(item)) > 0)
                {
                    continue;
                }
                ret += string.Format(" {0}", str);
            }
            return ret.Trim();
        }
        #endregion

        public static string GetInnerHtml(this ControlCollection controls)
        {
            StringWriter writer = new StringWriter();
            HtmlTextWriter writer2 = new HtmlTextWriter(writer);
            foreach (Control item in controls)
            {
                item.RenderControl(writer2);
            }
            return writer.ToString();
        }
    }

    public class ScriptComparer : IEqualityComparer<ScriptReference>
    {
        #region IEqualityComparer<ScriptReference> Members

        public bool Equals(ScriptReference x, ScriptReference y)
        {
            if (x == null || y == null)
            {
                return x == null && y == null ? true : false;
            }
            return x.ToString() == y.ToString();
        }

        public int GetHashCode(ScriptReference obj)
        {
            return obj.ToString().GetHashCode();
        }

        #endregion
    }

    #region public enum ResourceType
    /// <summary>
    /// 
    /// </summary>
    public enum ResourceType
    {
        Javascript,
        Stylesheet
    }
    #endregion
}
